<?php

class Bookmarks_Object_Bookmark {

	//these data are stored in the structured data
	protected $_id;
	protected $_private;

	//other data are stored in a blob and need to be extracted
	protected $_userId;
	protected $_userEmail;
	protected $_userPseudo;
	protected $_URL;
	protected $_publishedTime;
	protected $_title;
	protected $_description;
	protected $_tags = array();  

    
	/* definition of the  bookmarks table */
	static protected $_dbBookmarksTableName = 'bookmarks';
	static protected $_dbBookmarksIdCol = 'id';
	static protected $_dbBookmarksDataCol = 'data';
	static protected $_dbBookmarksPrivateCol = 'private';
	
	//following data are stored in the data blob and need to be extracted
	static protected $_dbBookmarkURLElem = 'URL';
	static protected $_dbBookmarkDateElem = 'date'; 
	static protected $_dbBookmarkTitleElem = 'title'; 
	static protected $_dbBookmarkDescriptionElem = 'description'; 
	
	static protected $_dbBookmarkTagsGroup = 'tags'; 
	
	static protected $_dbBookmarkUserGroup = 'user';
	static protected $_dbBookmarkUserEmailElem = 'email';
	static protected $_dbBookmarkUserIdElem = 'id';
	static protected $_dbBookmarkUserPseudoElem = 'pseudo';

	/* need to put a limit to the number of pages that can be retrieved */
	static protected $_limitPage = 1000;	

	public function __construct($id,$data = array()) {
		//if the user provides only the id, we need to go to the db to retrieve the data
		if(empty($data)) {
		  //We validate that the id is numeric
		  $validator = new Zend_Validate_Digits();
		  if(!$validator->isValid($id)) {
			require_once('bookmarks/objects/Exception.php');
			throw new Bookmark_Object_Exception('Invalid identifier',8); 	  	
		  }

		  //We get the data from the database
		  //bookmarkes are not sharded, best would be a distributed storage for the bookmark
		  //what will be sharded are the following, list and other features where just the id of the bookmark is needed
		  $db = self::_getDb();
		  $select = $db->select()->from(self::$_dbBookmarksTableName);
		  $select->where($db->quoteInto(self::$_dbBookmarksIdCol ." = ?", $id));
		  $res = $db->fetchAll($select->__toString());

		  if(empty($res)) {
			require_once('bookmarks/objects/Exception.php');
			throw new Bookmark_Object_Exception('Bookmark not found',123); 
		  }
		$data = $res[0];
		}

		$this->_id = $data[self::$_dbBookmarksIdCol];
		$this->_private = $data[self::$_dbBookmarksPrivateCol];

		$decodedBlob = self::decodeBookmark($data[self::$_dbBookmarksDataCol]);

		$this->_URL = $decodedBlob[self::$_dbBookmarkURLElem];
		$this->_publishedTime = $decodedBlob[self::$_dbBookmarkDateElem];
		$this->_title = $decodedBlob[self::$_dbBookmarkTitleElem];
		$this->_description = $decodedBlob[self::$_dbBookmarkDescriptionElem];
		
		
		//get user details
		$this->_userId = $decodedBlob[self::$_dbBookmarkUserGroup][self::$_dbBookmarkUserIdElem]; 
		$this->_userEmail = $decodedBlob[self::$_dbBookmarkUserGroup][self::$_dbBookmarkUserEmailElem];
		$this->_userPseudo = $decodedBlob[self::$_dbBookmarkUserGroup][self::$_dbBookmarkUserPseudoElem];

		//get tags for the bookmark
		$this->_tags = $decodedBlob[self::$_dbBookmarkTagsGroup];

	} //end __construct
  
  
  
  
  
  
    //The usual getter
  public function getId() {
    return $this->_id;	
  }
  public function getURL() {
    return $this->_URL;	
  }
  public function getTitle() {
    return $this->_title;	
  }
  public function getDescription() {
    return $this->_description;	
  }
  public function getUserId() {
    return $this->_userId;	
  } 
  public function getuserEmail() {
    return $this->_userEmail;	
  }  
  public function getuserPseudo() {
    return $this->_userPseudo;	
  }  
  public function getpublishedTime() {
    return $this->_publishedTime;	
  }   

  //tags are stored in lowercase
  public function getTags() {
    return $this->_tags;	
  }  

  public function isPrivate() {
    return (bool) $this->_private;	
  } 
  
  
  //Object model when returned in the API
  public function asArray() {
  	$results = array();

    $results['id'] = $this->getId();
    $results['URL'] = $this->getURL();
    $results['title'] = $this->getTitle();
    $results['description'] = $this->getDescription();
    $results['publishedTime'] = $this->getpublishedTime();
    $results['private'] = $this->isPrivate();	
    $results['user']['id'] = $this->getUserId(); 
    $results['user']['email'] = $this->getuserEmail(); 
    $results['user']['pseudo'] = $this->getuserPseudo();
    $results['tags'] = $this->getTags();  	
    return $results;
  }
  





  static public function formatPage($page = 1) {
      if(empty($page)) $page = 1;
      $validator = new Zend_Validate_Digits();
      if(!$validator->isValid($page)) {
  		require_once('status/objects/Exception.php');
  		throw new Status_Object_Exception('invalid input parameters',15);      	
  	  }
  	  if(self::$_limitPage < $page) {
  		require_once('status/objects/Exception.php');
  		throw new Status_Object_Exception('page search is limited to '.self::$_limitPage.' pages',15);   	  	
  	  }
  	  
  	  if($page < 1 ) $page = 1;
  	  return $page;		
  }

  static public function formatPerPage($perPage = 20) {
      if(empty($perPage)) $perPage = 20;
      $validator = new Zend_Validate_Digits();
      if(!$validator->isValid($perPage)) {
  		require_once('status/objects/Exception.php');
  		throw new Status_Object_Exception('invalid input parameters',15);      	
  	  }
  	  if($perPage < 1 ) $perPage = 1;
  	  if($perPage > 100 ) $perPage = 100;
  	  return $perPage;
  }





  /* listing() - used to retrieve the latest public bookmarks
  	@param perPage - number of statuses to return
  	@param page - num of the page to return
  	@return array of php statuses	 
  */
	
  static public function listing($perPage = 20,$page = 1) {
      
      //We validate the inputs
 	  $page = self::formatPage($page);
 	  $perPage = self::formatPerPage($perPage); 	  
  	  
  	  $db = self::_getDb();
      $select = $db->select()->from(self::$_dbBookmarksTableName)->limitPage($page, $perPage);
      $select->where($db->quoteInto(self::$_dbBookmarksPrivateCol ." = ?", false));
      $select->order(self::$_dbBookmarksIdCol.' DESC');
	  
      $res = $db->fetchAll($select->__toString());    	  

      if(empty($res)) return array();

      //We must reinterpret the rows so it is independant to the way it is stored
      $results = array();
      foreach($res as $num => $row) {
    	try{
	    	$bookmark = new Bookmarks_Object_Bookmark('',$row);
	    	$results[] = $bookmark->asArray();
    	} catch(Bookmark_Object_Exception $e){
    			Zend_Registry::get('dt_log')->log('bookmark issue:'.serialize($row), Zend_Log::ERROR);
    	}
	    		
      } //end foreach
    
      return $results;
      
  } //end listing




  
  
  
	//delete - delete the bookmark from the bookmark database (only the bookmark, not the timeline)
	//@input void
	//@return int number of rows deleted (should be 1) if not 1 you better do something

	public function delete() {
		//First we validate that the authenticated user is the owner of the bookmark
		$userId = (int) Zend_Registry::get('app_user')->getId();
		if($this->getUserId() != $userId) {
			require_once('bookmarks/objects/Exception.php');
			throw new Bookmark_Object_Exception('user can\'t delete link',15);		
		}
		//We go to the database and delete the bookmark
		$db = self::_getDb();
		return $db->delete(self::$_dbBookmarksTableName,$db->quoteInto(self::$_dbBookmarksIdCol.' = ?',$this->getId()));
	
	} 








  /* is Visible() - tells if the user is allowed to see it or not.
     use cases are if the status is public or the user is owner of the status (could think of other use cases later)
     */
  
  public function isVisible() {
  	//first use case the status is public
  	if(!$this->isPrivate()) return true;
  	
  	//second use case the user is the owner of the status
	$email = Zend_Registry::get('app_user')->getEmail();
  	if($this->getuserEmail() == $email) return true;
  	
  	
  	return false;
  }




  
  
  
	  /* create() - as said in the name, create is responsible for creating a bookmark in the system. 
		 only user index is done, other indexing like follower and following ones are left to the controller (asynchronously is better)
		 @param text - bookmarkText
		 @throw Bookmark_Object_Exception if the bookmark is empty after purification
		 @throw Zend_Db_Exception if an issue occurs while putting the message in database
		 @return Numeric identifier of the tweet
	  */
	static public function create($URL,$tags = '',$title = '',$description = '',$isPrivate = false,$userDetails = array()) {

	$validator = new Zend_Validate_Callback(array('Zend_Uri', 'check'));
	if (!$validator->isValid($URL)) {
		require_once('bookmarks/objects/Exception.php');
		throw new Bookmark_Object_Exception('Bookmark URL is invalid:'.$URL,145);  			
	}
	//We check that the tags are valid
	$tagsList = array();
	if(!empty($tags)) {
		$potentialTags = preg_split('/,/',$tags);
		foreach($potentialTags as $num => $potentialTag) {
			 $tagMatched = preg_match(  '/^#?([a-zA-z0-9_]+)$/i'  ,  trim(strtolower($potentialTag)) ,  $match);
			if($tagMatched) {
				$tagsList[] = $match[1];
			}
		}
	}

	
  	//we check that the bookmark title and description are sane
  	//We clean the description, only text is allowed
	if(!empty($title) || !empty($description)) {
		require_once('htmlpurifier/library/HTMLPurifier.auto.php');
		$config = HTMLPurifier_Config::createDefault();
		$config->set('HTML.Allowed', '');
		$purifier = new HTMLPurifier($config);
		$title = $purifier->purify(trim($title));
		$description = $purifier->purify(trim($description));
	}
	
	$isPrivate = (bool) $isPrivate;
	
	
  	//we need to get the email of the user
	if(empty($userDetails)) {
		$email = Zend_Registry::get('app_user')->getEmail();
		$userId = (int) Zend_Registry::get('app_user')->getId();
		$pseudo = Zend_Registry::get('app_user')->getPseudo();	
	} else {
		$email = $userDetails['email'];
		$userId = (int) $userDetails['id'];
		$pseudo = $userDetails['pseudo'];
	}
  	//the date is calculated based on time that gives the current time
  	$publishedTime = time();

	 	
  	//we encode the blob and stores it
  	$data = array(
  		self::$_dbBookmarkURLElem => $URL,
		self::$_dbBookmarkTitleElem => $title,
		self::$_dbBookmarkDescriptionElem => $description,
  		self::$_dbBookmarkDateElem => $publishedTime,
		self::$_dbBookmarkUserGroup => array(
			self::$_dbBookmarkUserIdElem => $userId,
			self::$_dbBookmarkUserEmailElem => $email,
			self::$_dbBookmarkUserPseudoElem => $pseudo,
		),
		self::$_dbBookmarkTagsGroup => $tagsList			
  	);
	
	
	Zend_Registry::get('dt_log')->log(serialize($data), Zend_Log::DEBUG);
  
  	//we encode the blob
  	$blobData = self::encodeBookmark($data);
  	
  	//we store it in the data column
  	$toBesaved = array(self::$_dbBookmarksDataCol => $blobData,self::$_dbBookmarksPrivateCol => $isPrivate);
  	
  	//and we put it in the database
  	$db = self::_getDb();
  	
    $db->insert(self::$_dbBookmarksTableName, $toBesaved);
    
    //we get the last inserted Id
    $bookmarkId = $db->lastInsertId();
    
    //we return the id of the bookmark (which controller can use to regenerate the bookmark)
  	return $bookmarkId;

  } //end create
  
  
  
	public function update($params) {
		
		//bookmarks can only be updated by the owner
		$userId = (int) Zend_Registry::get('app_user')->getId();
		if($userId != $this->getUserId()) {
			require_once('bookmarks/objects/Exception.php');
			throw new Bookmark_Object_Exception('Bookmark can only be updated by the bookmark owner',12); 
		}
		
		if(!empty($params['URL'])) {
			$validator = new Zend_Validate_Callback(array('Zend_Uri', 'check'));
			if (!$validator->isValid($params['URL'])) {
				require_once('bookmarks/objects/Exception.php');
				throw new Bookmark_Object_Exception('Bookmark URL is invalid:'.$URL,145);  			
			}
			$URL = 	$params['URL'];	
		} else $URL = $this->getURL();
		
		
		
		//We check that the tags are valid
		if(!empty($params['tags'])) {
		
			$tagsList = array();
			$potentialTags = preg_split('/,/',$params['tags']);
			foreach($potentialTags as $num => $potentialTag) {
				 $tagMatched = preg_match(  '/^#?([a-zA-z0-9_]+)$/i'  ,  trim(strtolower($potentialTag)) ,  $match);
				if($tagMatched) {
					$tagsList[] = $match[1];
				}
			}
		} else $tagsList = $this->getTags();
		

		
		//we check that the bookmark title and description are sane
		//We clean the description, only text is allowed
		if(!empty($params['title'])) {
			require_once('htmlpurifier/library/HTMLPurifier.auto.php');
			$config = HTMLPurifier_Config::createDefault();
			$config->set('HTML.Allowed', '');
			$purifier = new HTMLPurifier($config);
			$title = $purifier->purify(trim($params['title']));
		} else $title = $this->getTitle();


		
		//we check that the bookmark title and description are sane
		//We clean the description, only text is allowed
		if(!empty($params['description'])) {
			require_once('htmlpurifier/library/HTMLPurifier.auto.php');
			$config = HTMLPurifier_Config::createDefault();
			$config->set('HTML.Allowed', '');
			$purifier = new HTMLPurifier($config);
			$description = $purifier->purify(trim($params['description']));
		} else  $description = $this->getDescription();

		
		if(!empty($params['isPrivate'])) $isPrivate = (bool) $params['isPrivate'];
		else $isPrivate = $this->isPrivate();
		
		
		//we need to get the email of the user
		//no change on those fields
		$userId = $this->getUserId();
		$email= $this->getuserEmail();
		$pseudo= $this->getuserPseudo();

		//the date is calculated based on time that gives the current time
		$publishedTime = $this->getpublishedTime();

			
		//we encode the blob and stores it
		$data = array(
			self::$_dbBookmarkURLElem => $URL,
			self::$_dbBookmarkTitleElem => $title,
			self::$_dbBookmarkDescriptionElem => $description,
			self::$_dbBookmarkDateElem => $publishedTime,
			self::$_dbBookmarkUserGroup => array(
				self::$_dbBookmarkUserIdElem => $userId,
				self::$_dbBookmarkUserEmailElem => $email,
				self::$_dbBookmarkUserPseudoElem => $pseudo,
			),
			self::$_dbBookmarkTagsGroup => $tagsList			
		);
		
		
	  
		//we encode the blob
		$blobData = self::encodeBookmark($data);
		
		//we store it in the data column
		$toBesaved = array(self::$_dbBookmarksDataCol => $blobData,self::$_dbBookmarksPrivateCol => $isPrivate);
		
		$where = self::$_dbBookmarksIdCol.' = '.$this->getId();
		//and we put it in the database
		$db = self::_getDb();
		$db->update(self::$_dbBookmarksTableName, $toBesaved,$where);
		
		//we don t return anything, if issue occured, an exception is thrown
	}//end update
  
  
  
	static public function createFromStatus($aStatus) {
		//we extract the Urls from the status
		$reg = '/(http|https|ftp|winaproach):\/\/[A-Za-z0-9\.]+(:\d+)?\/?([^ ]*)/';
		$statusText = $aStatus['status'];
		preg_match_all  (  $reg  , $statusText  , $matches);
		if(empty($matches)) {
		    require_once('bookmark/objects/Exception.php');
			throw new Bookmark_Object_Exception('No URL match found in the status',78);
		}
		
		//we filter the tags to keep the ones that are not bookmark and link
		$tagsList = array();
		foreach($aStatus['tags'] as $num => $tag) {
			if($tag != 'link' && $tag != 'bookmark') $tagsList[] = $tag;
		}
		//APi requires tag to be separated by coma
		$tagsString = implode(',',$tagsList);
		$private = (bool) $aStatus['private'];
		$description = 'created from status '.$aStatus['id'];
		$idList = array();
		foreach($matches[0] as $num => $url) {
			try{
				$idList[] = self::create($url,$tagsString,'',$description,$private,$aStatus['user']);
			} catch(Bookmark_Object_Exception $e) {
				Zend_Registry::get('dt_log')->log('Issue while creating bookmark from status '.$aStatus['id'].': '.$e->getMessage(),Zend_Log::ERROR);	
			}
		}
		
		return $idList;
		
	}
  
  
  
  
  
  
  
    //the message is stored as a base64 gzdefalted serialized PHP
  static public function decodeBookmark($data) {
	
    $decoded = @unserialize(gzinflate(base64_decode($data)));
    if(empty($decoded)) {
      require_once('bookmark/objects/Exception.php');
      throw new Bookmark_Object_Exception('An issue occured while decoding message',16);
    }  
    return  $decoded;	
  }

  static public function encodeBookmark($data) {
  	if(!is_array($data)) {
  		require_once('bookmark/objects/Exception.php');
  		throw new Bookmark_Object_Exception('input parameter is not an array',12); 
  	}
    $encoded = @base64_encode(gzdeflate(serialize($data),9));
    if(empty($encoded)) {
    	require_once('bookmark/objects/Exception.php');
    	throw new Bookmark_Object_Exception('An issue occured while encoding message',17);
    } 
    return $encoded;   	
  }
  
  static protected function _getDb() {
  	return Zend_Registry::get('dt_dbs')->getDb('bookmarks','bookmarks','key');	
  }
  
  
  

} //end class